package com.lyx.question.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lyx.common.constant.*;
import com.lyx.common.exception.BusinessException;
import com.lyx.common.exception.ExceptionEnum;
import com.lyx.common.result.R;
import com.lyx.common.utils.JwtUtils;
import com.lyx.question.dto.*;
import com.lyx.question.entity.Paper;
import com.lyx.question.entity.Question;
import com.lyx.question.entity.QuestionRecord;
import com.lyx.question.entity.Subject;
import com.lyx.question.feign.UserFeignService;
import com.lyx.question.mapper.PaperMapper;
import com.lyx.question.mapper.QuestionMapper;
import com.lyx.question.mapper.QuestionRecordMapper;
import com.lyx.question.mapper.SubjectMapper;
import com.lyx.question.mq.PaperMessage;
import com.lyx.question.mq.Product;
import com.lyx.question.service.PaperService;
import com.lyx.question.service.QuestionRecordService;
import com.lyx.question.service.QuestionSmsService;
import io.jsonwebtoken.lang.Collections;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 14667
 * @date 2022/7/26 9:58
 */
@Component
@Slf4j
public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements PaperService {

    @Autowired
    QuestionMapper questionMapper;

    @Autowired
    SubjectMapper subjectMapper;

    @Autowired
    QuestionRecordMapper questionRecordMapper;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    QuestionRecordService questionRecordService;

    @Autowired
    Product product;

    @Autowired
    UserFeignService userFeignService;

    @Autowired
    QuestionSmsService questionSmsService;


    @Override
    @Transactional
    public PaperWitnQuestionDto generatePaperByChapter(HttpServletRequest request, Integer chapterId) {
        //这里不在需要校验了  因为在网关层已经统一进行了校验，能到这id肯定是正确的了
        String id = request.getHeader(RedisContant.USER_ID);
        Integer userId = Integer.parseInt(id);
        //这里应该验证chapter_id需要为三级
        Subject subject = subjectMapper.selectOne(new QueryWrapper<Subject>().eq("id", chapterId));
        if(Objects.isNull(subject)){
            throw new BusinessException(ExceptionEnum.CHAPTER_NOT_EXIST);
        }
        if(!subject.getChapterType().equals(ChapterConstant.THREE_SUBJECT_TYPE)){
            throw new BusinessException(ExceptionEnum.CHAPTER_TYPE_ERROR);
        }
        //按照规则从题目中生成试卷中的题目，规则是：章节内随机10道题
        List<Question> questionList = questionMapper.randomQuestionInchapter(chapterId,QuestionConstant.SELECTION_COUNT);
        if (Collections.isEmpty(questionList)){
            throw new BusinessException(ExceptionEnum.NO_QUESTION_IN_CHAPTER);
        }
        questionList = questionList.stream().sorted(Comparator.comparing(Question::getQuestionType)).collect(Collectors.toList());
        //首先操作试卷表生成试卷,做题记录以及封装返回的数据
        PaperWitnQuestionDto questionDtos = getQuestionDtos(userId, chapterId, questionList);
        return questionDtos;
    }

    private PaperWitnQuestionDto getQuestionDtos(Integer userId, Integer chapterId, List<Question> questionList) {
        //从生成的试卷中分类出单选、多选、不定项
        long singleQuestion = questionList.stream().filter(question -> question.getQuestionType().equals(QuestionConstant.SINGLE_SELECTION)).count();
        long multiQuestion = questionList.stream().filter(question -> question.getQuestionType().equals(QuestionConstant.MULTIPLE_SELECTION)).count();
        long Indefinite = questionList.stream().filter(question -> question.getQuestionType().equals(QuestionConstant.INDEFINITE_SELECTION)).count();
        Paper paper = new Paper();
        LocalDateTime now = LocalDateTime.now();
        //获得试卷的答题时间
        long time = singleQuestion*QuestionConstant.SINGLE_EXAM_TIME+
                multiQuestion*QuestionConstant.MULTIPLE_EXAM_TIME+
                Indefinite*QuestionConstant.INDEFINITE_EXAM_TIME;
        //获得试卷的总分
        Double totalScore = singleQuestion*QuestionConstant.SINGLE_SELECTION_SCORE+multiQuestion*QuestionConstant.MULTIPLE_SELECTION_SCORE
                +Indefinite*QuestionConstant.INDEFINITE_SELECTION_SCORE;
        //获取二级和三级的chapterName（这里对于章节内出题的话是二级三级名，对于随机出题的话是一级和二级章节名）
        Subject subjectThree = subjectMapper.selectById(chapterId);
        Subject subjectTwo = subjectMapper.selectById(subjectThree.getParentId());
        //设置提交时间、总的答卷时间、问题的个数、总分、用户id、用户得分、二三级章节名、完成状态
        paper.setCommitTime(now.plusMinutes(time));
        paper.setTotalTime((int)time);
        paper.setQuestionNum((int) (singleQuestion+multiQuestion+Indefinite));
        paper.setTotalScore(totalScore);
        paper.setUserId(userId);
        paper.setChapterThreeName(subjectThree.getChapterName());
        paper.setChapterTwoName(subjectTwo.getChapterName());
        int insert = baseMapper.insert(paper);
        if(insert<=0){
            throw new BusinessException(ExceptionEnum.GREATE_PAPER_ERROR);
        }
//        Paper paper1 = baseMapper.selectOne(new QueryWrapper<Paper>().eq("user_id", userId).orderByDesc("id").last("limit 1"));
        //生成做题记录表保存试卷的信息
        log.info("paperId:"+paper.getId());
        ArrayList<QuestionRecord> questionRecordList = new ArrayList<>();
        for(int i = 1;i<=questionList.size();i++){
            QuestionRecord questionRecord = new QuestionRecord();
            Question question  = questionList.get(i-1);
            questionRecord.setChapterId(question.getChapterId());
            questionRecord.setQuestionId(question.getId());
            questionRecord.setPaperId(paper.getId());
            questionRecord.setUserId(userId);
            questionRecord.setUserAnswer("");
            questionRecord.setQuestionType(question.getQuestionType());
            questionRecord.setQuestionDescription(question.getQuestionDescription());
            questionRecord.setChoices(question.getChoices());
            questionRecord.setNumber(i);
            questionRecordList.add(questionRecord);
        }
        //批量生成做题记录
        boolean b = questionRecordService.saveBatch(questionRecordList);
        System.out.println(questionRecordList.get(0).getId());
        log.info("b:"+b);
        //从数据库中根据paperId按照number的顺序查出题目信息
        log.info("questionRecordList1:"+questionRecordList.size());
        //封装返回的试卷信息
        List<QuestionDto> collect = questionRecordList.stream().map(question -> {
            QuestionDto questionDto = new QuestionDto();
            BeanUtils.copyProperties(question, questionDto);
            return questionDto;
        }).collect(Collectors.toList());
        log.info("collect:"+collect.size());
        PaperWitnQuestionDto paperWitnQuestionDto = new PaperWitnQuestionDto();
        paperWitnQuestionDto.setQuestionDtoList(collect);
        paperWitnQuestionDto.setCreateTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(paper.getCommitTime().minusMinutes(time)));
        paperWitnQuestionDto.setCommitTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(paper.getCommitTime()));
        paperWitnQuestionDto.setPaperId(paper.getId());
        paperWitnQuestionDto.setUserId(userId);

        //定时交卷
        PaperMessage paperMessage = new PaperMessage();
        paperMessage.setPaperId(paper.getId());
        paperMessage.setUserId(userId);
        String jsonString = JSON.toJSONString(paperMessage);
        //定时发送消息
        try {
            product.scheduleSendMessage(jsonString,time*60);
        } catch (Exception e) {
            throw new BusinessException(ExceptionEnum.COMMIT_PAPER_ERROR);
        }
        //将试卷按照用户id_试卷id保存到缓存，里面存放的是试卷中题目的记录以及每道题的状态
        redisTemplate.opsForHash().put(RedisContant.QUESTION_RECORD_CACHE,paper.getUserId().toString()+"_"+paper.getId().toString(), JSON.toJSONString(paperWitnQuestionDto));
        return paperWitnQuestionDto;
    }


    @Override
    @Transactional
    public PaperWitnQuestionDto randomGeneratePaper(HttpServletRequest request, Integer chapterId) {
        //这里不在需要校验了  因为在网关层已经统一进行了校验，能到这id肯定是正确的了
        String id = request.getHeader(RedisContant.USER_ID);
        Integer userId = Integer.parseInt(id);
        //这里应该验证chapter_id需要为三级
        Subject subject1 = subjectMapper.selectOne(new QueryWrapper<Subject>().eq("id", chapterId));
        if(Objects.isNull(subject1)){
            throw new BusinessException(ExceptionEnum.CHAPTER_NOT_EXIST);
        }
        if(!subject1.getChapterType().equals(ChapterConstant.TWO_SUBJECT_TYPE)){
            throw new BusinessException(ExceptionEnum.CHAPTER_TYPE_ERROR);
        }
        //按照规则从题目中生成试卷中的题目，规则是：随机生成20道对应科目的题目，要求题目中单选12、多选5，不定项3道
        //先根据chapter_id找到三级chapter_id
        List<Subject> subjects = subjectMapper.selectList(new QueryWrapper<Subject>().eq("parent_id", chapterId));
        List<Integer> chapterIds = subjects.stream().map(subject -> {
            return subject.getId();
        }).collect(Collectors.toList());
        if(Collections.isEmpty(chapterIds)){
            throw new BusinessException(ExceptionEnum.NO_QUESTION_IN_CHAPTER);
        }
        List<Question> questionList = new ArrayList<>();
        List<Question> singleQuestionList = questionMapper.randomQuestionByTypeInChapter(QuestionConstant.SINGLE_SELECTION,QuestionConstant.SINGLE_SELECTION_COUNT,chapterIds);
        List<Question> multiQuestionList = questionMapper.randomQuestionByTypeInChapter(QuestionConstant.MULTIPLE_SELECTION,QuestionConstant.MULTIPLE_SELECTION_COUNT,chapterIds);
        List<Question> indefiniteQuestionList = questionMapper.randomQuestionByTypeInChapter(QuestionConstant.INDEFINITE_SELECTION,QuestionConstant.INDEFINITE_SELECTION_COUNT,chapterIds);
        //分别将单选、多选、不定项的题目查出来添加到题目集合中
        questionList.addAll(singleQuestionList);
        questionList.addAll(multiQuestionList);
        questionList.addAll(indefiniteQuestionList);
//        System.out.println(questionList);
        //首先操作试卷表生成试卷
        PaperWitnQuestionDto questionDtos = getQuestionDtos(userId, chapterId, questionList);
        return questionDtos;
    }


    @Override
    @Transactional
    public void submitPaper(PaperWitnQuestionDto paperWitnQuestionDto, HttpServletRequest request) {
        //这里不在需要校验了  因为在网关层已经统一进行了校验，能到这id肯定是正确的了
        String id = request.getHeader(RedisContant.USER_ID);
        Integer userId = Integer.parseInt(id);
        if(Objects.isNull(paperWitnQuestionDto)){
            throw new BusinessException(ExceptionEnum.IS_NOT_NULL.getCode(),String.format(ExceptionEnum.IS_NOT_NULL.getMsg(),"答题记录为空"));
        }
        Integer paperId = paperWitnQuestionDto.getPaperId();
        Integer userId1= paperWitnQuestionDto.getUserId();
        List<QuestionDto> questionDtoList = paperWitnQuestionDto.getQuestionDtoList();
        //如果解析出来的用户和本地传过来的id不一致，说明用户信息出错
        if(!userId.equals(userId1)){
            throw new BusinessException(ExceptionEnum.USER_NOT_LOGIN);
        }
        //批量保存做题记录
        executeSubmit(paperId, userId, questionDtoList);
    }

    @Override
    public PaperResultDto getPaperResult(Integer paperId, HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        String id = JwtUtils.getMemberIdByJwtToken(request);
        Integer userId = null;
        if(!id.equals("")){
            userId = Integer.parseInt(id);
            String s = redisTemplate.opsForValue().get(String.format(RedisContant.TOKEN, userId));
            if(!jwtToken.equals(s)){
                throw new BusinessException(ExceptionEnum.USER_NOT_LOGIN);
            }
        }else{
            throw new BusinessException(ExceptionEnum.USER_NOT_LOGIN);
        }
        //根据paperId获取试卷信息
        Paper paper = baseMapper.selectById(paperId);
        if (Objects.isNull(paper)){
            throw new BusinessException(ExceptionEnum.NOT_EXIST.getCode(),String.format(ExceptionEnum.NOT_EXIST.getMsg(),"试卷"));
        }
        if(!paper.getFinishStatus().equals(1)){
            throw new BusinessException(ExceptionEnum.PAPER_NOT_FINISH);
        }
        PaperResultDto paperResultDto = new PaperResultDto();
        BeanUtils.copyProperties(paper,paperResultDto);
        paperResultDto.setPaperId(paper.getId());
        //联表查询做题记录表和题库表封装批改结果
        List<QuestionResultDto> questionResultDtoList = questionRecordMapper.getCorrectingResults(paperId);
        paperResultDto.setQuestionResultDtoList(questionResultDtoList);
        return paperResultDto;
    }

    @Override
    @Transactional
    public void autoSubmitPaper(PaperWitnQuestionDto paperWitnQuestionDto) {
        Integer paperId = paperWitnQuestionDto.getPaperId();
        Integer userId= paperWitnQuestionDto.getUserId();
        List<QuestionDto> questionDtoList = paperWitnQuestionDto.getQuestionDtoList();
        //批量保存做题记录
        if(Objects.isNull(paperWitnQuestionDto)){
            throw new BusinessException(ExceptionEnum.IS_NOT_NULL.getCode(),String.format(ExceptionEnum.IS_NOT_NULL.getMsg(),"答题记录为空"));
        }
        executeSubmit(paperId, userId, questionDtoList);
    }

    private void executeSubmit(Integer paperId, Integer userId, List<QuestionDto> questionDtoList) {
        Paper paper = baseMapper.selectById(paperId);
        if(paper.getFinishStatus()==1){
            //避免重复提交,如果已经交卷了就不需要执行了
            return;
        }
        paper.setFinishStatus(1);

        List<QuestionRecord> questionRecordList = questionDtoList.stream().map(questionDto -> {
            QuestionRecord questionRecord = new QuestionRecord();
            BeanUtils.copyProperties(questionDto, questionRecord);
            return questionRecord;
        }).collect(Collectors.toList());
        boolean b = questionRecordService.updateBatchById(questionRecordList);

        if (!b){
            throw new BusinessException(ExceptionEnum.QUESTION_RECORD_SAVE_ERROR);
        }
        //收集question_id
        List<Integer> questionIds = questionRecordList.stream().map(questionRecord -> {
            return questionRecord.getQuestionId();
        }).collect(Collectors.toList());
        Double userScore = 0d;
        List<Integer> correctQuestionTypeList = questionMapper.correctQuestiontypeList(paperId);
        for(Integer  correctQuestionType:correctQuestionTypeList){
            if(correctQuestionType.equals(QuestionConstant.SINGLE_SELECTION)){
                userScore+=QuestionConstant.SINGLE_SELECTION_SCORE;
            }else if(correctQuestionType.equals(QuestionConstant.MULTIPLE_SELECTION)){
                userScore+=QuestionConstant.MULTIPLE_SELECTION_SCORE;
            }else{
                userScore+=QuestionConstant.INDEFINITE_SELECTION_SCORE;
            }
        }
        //修改试卷基本信息并保存试卷

        paper.setUserScore(userScore);
        paper.setCommitTime(LocalDateTime.now());
        paper.setCorrectNum(correctQuestionTypeList.size());
        baseMapper.updateById(paper);
        //更新做题排名
        LocalDate now = LocalDate.now();
        redisTemplate.opsForZSet().incrementScore(now.toString(), userId.toString(), paper.getQuestionNum());
        redisTemplate.expire(now.toString(),8, TimeUnit.DAYS);
        redisTemplate.opsForZSet().incrementScore(RedisContant.QUESTION_NUM_WEEK_RANK+"_"+now.toString(), userId.toString(),paper.getQuestionNum());
        redisTemplate.expire(RedisContant.QUESTION_NUM_WEEK_RANK+"_"+now.toString(),8,TimeUnit.DAYS);
        //这里不去刷新排行榜，等用户跳转到排行榜页面的时候再去统一更新

        R userInfoById = userFeignService.getUserInfoById(userId);
        if(userInfoById.getSuccess().equals("false")){
            log.error("用户不存在");
            return;
        }
        UserDto userDto = JSON.parseObject(JSONObject.toJSONString(userInfoById.getData().get("data")), UserDto.class);
        boolean flag = questionSmsService.sendMessage(userDto.getPhone(), paperId);
        if(!flag){
            log.error("发送短信失败");
        }
    }
}
